<?php

namespace yunj\core\traits;

use think\Collection;
use think\db\BaseQuery;
use think\db\Raw;
use think\Model;
use think\Model as ThinkModel;
use think\facade\Db;

trait ModelHelper {

    /**
     * 构建条件
     * @param BaseQuery|mixed $query
     * @param mixed $where
     * @param ThinkModel|null $model
     * @return BaseQuery|mixed
     */
    public function buildWhere(&$query, $where, ?Model $model = null) {
        if (!$where) return $query;
        if (is_int($where)) {
            if (!$model) throw new \RuntimeException("Missing '\$model' parameter for 'buildWhere' method");
            $query->where($model->getPk(), '=', $where);
            return $query;
        }

        if (is_string($where)) {
            $where = trim($where);
            if (preg_match('/\s/', $where)) {
                // 有空格
                $query->whereRaw($where);
            } else {
                // 没有空格
                if (!$model) throw new \RuntimeException("Missing '\$model' parameter for 'buildWhere' method");
                $query->where($model->getPk(), '=', $where);
            }
            return $query;
        }

        if (!is_array($where)) throw new \RuntimeException("'buildWhere' method '\$where' parameter error");

        foreach ($where as $v) {
            if (is_array($v) && count($v) === 3) {
                list($field, $op, $value) = $v;
                switch (strtoupper($op)) {
                    case 'IN':
                        $query->whereIn($field, $value);
                        break;
                    case 'NOT IN':
                        $query->whereNotIn($field, $value);
                        break;
                    case 'like':
                        $query->whereLike($field, $value);
                    case 'not like':
                        $query->whereNotLike($field, $value);
                        break;
                    default:
                        $query->where($field, $op, $value);
                }
                continue;
            }
            if (is_array($v) && count($v) === 2) {
                list($field, $value) = $v;
                switch (strtoupper($value)) {
                    case 'IS NULL':
                        $query->whereNull($field);
                        break;
                    case 'IS NOT NULL':
                        $query->whereNotNull($field);
                        break;
                }
                continue;
            }
            if (is_string($v)) {
                $query->whereRaw($v);
                continue;
            }
            if (is_callable($v)) {
                $query->where($v);
                continue;
            }
        }
        return $query;
    }

    /*---------------------------------------------获取数据------------------------------------------------------------*/

    /**
     * Notes: 获取本表数据条数
     * Author: Uncle-L
     * Date: 2020/2/15
     * Time: 18:08
     * @param array $where
     * @return int
     */
    public function getOwnCount(array $where = []): int {
        /** @var ThinkModel $this */
        $query = $this->newQuery();
        $query = $this->buildWhere($query, $where);
        $count = $query->count();
        return $count;
    }

    /**
     * Notes: 获取本表某个字段的值
     * Author: Uncle-L
     * Date: 2020/2/15
     * Time: 18:09
     * @param array $where
     * @param string $field
     * @param string|array|Raw $order
     * @return mixed
     */
    public function getFieldValue(string $field,array $where = [],$order = []) {
        /** @var ThinkModel $this */
        $query = $this->newQuery();
        $query = $this->buildWhere($query, $where);
        $data = $query->order($order)->value($field);
        return $data;
    }

    /**
     * Notes: 获取本表某一个/几个字段的值，也可以设置元素key为某个字段
     * 示例1：->getColumnOptions('name',[...],[...],[...],'id')
     * 示例2：->getColumnOptions('concat(name,'_',age)',[...],[...],[...],'id')
     * Author: Uncle-L
     * Date: 2020/12/18
     * Time: 16:04
     * @param string|array $field
     * @param array $where
     * @param string|array|Raw $group
     * @param string|array|Raw $order
     * @param string $key
     * @return array
     */
    public function getColumnOptions($field, array $where = [], $group = [], $order = [], string $key = '') {
        /** @var ThinkModel $this */
        if (!$field) {
            return [];
        }
        $query = $this->newQuery();
        $query = $this->buildWhere($query, $where);
        $field = is_string($field) ? explode(',', $field) : $field;
        $data = $query->field($field)->group($group)->order($order);

        if ($key) {
            $data->field($key);
        }
        $data = $data->select();
        $data = $data ? $data->toArray() : [];
        if (!$data) {
            return $data;
        }
        if (count($field) == 1) {
            $data = array_column($data, current($field), $key ?: null);
        } else {
            $data = $key ? array_column($data, null, $key) : $data;
        }

        return $data;
    }

    /**
     * Notes: 获取本表单条数据
     * Author: Uncle-L
     * Date: 2020/7/20
     * Time: 16:36
     * @param array $where
     * @param array|string $field
     * @return array|Model|null|static|mixed
     */
    public function getOwnRow(array $where = [], $field = ["*"]) {
        /** @var ThinkModel $this */
        $query = $this->newQuery();
        $query = $this->buildWhere($query, $where);
        $data = $query->field($field)->find();
        return $data;
    }

    /**
     * 获取本表单条数据，并转换为数组
     * @param array $where
     * @param array|string $field
     * @return array
     */
    public function getOwnRowToArray(array $where = [], $field = ["*"]): array {
        $data = $this->getOwnRow($where, $field);
        if ($data && !is_array($data)) {
            $data = $data->toArray();
        }
        return $data ?: [];
    }

    /**
     * Notes: 获取本表多条数据
     * Author: Uncle-L
     * Date: 2020/7/20
     * Time: 16:36
     * @param array $where
     * @param string|string[] $field
     * @param string|array|Raw $order
     * @param int $page 分页
     * @param int $pageSize 分页数据量
     * @return Collection|array|static[]
     */
    public function getOwnRows(array $where = [], $field = ["*"], $order = [], int $page = 0, int $pageSize = 0) {
        /** @var ThinkModel $this */
        $query = $this->newQuery();
        $query = $this->buildWhere($query, $where);
        $datas = $query->field($field)->order($order);
        if ($page && $pageSize) $datas->limit(($page - 1) * $pageSize, $pageSize);
        $datas = $datas->select();
        return $datas;
    }

    /**
     * Notes: 获取本表多条数据，并转换为数组
     * Author: Uncle-L
     * Date: 2020/7/20
     * Time: 16:36
     * @param array $where
     * @param string|string[] $field
     * @param string|array|Raw $order
     * @param int $page 分页
     * @param int $pageSize 分页数据量
     * @return array
     */
    public function getOwnRowsToArray(array $where = [], $field = ["*"], $order = [], int $page = 0, int $pageSize = 0): array {
        $datas = $this->getOwnRows($where, $field, $order, $page, $pageSize);
        return $datas ? $datas->toArray() : [];
    }

    /*---------------------------------------------操作数据------------------------------------------------------------*/

    /**
     * Notes: 添加单条数据，若主键为自增id，则返回结果id；反之返回影响行数
     * Author: Uncle-L
     * Date: 2020/2/15
     * Time: 21:10
     * @param array $data
     * @param bool $incId [添加数据是否自增id]
     * @return int|string
     */
    public function addRow(array $data, bool $incId = true) {
        /** @var ThinkModel $this */
        $query = $this->strict(false);
        $res = $incId ? $query->insertGetId($data) : $query->insert($data);
        return $res;
    }

    /**
     * Notes: 添加多条数据，返回添加成功的条数
     * Author: Uncle-L
     * Date: 2020/2/15
     * Time: 21:11
     * @param array $data
     * @return int|string
     */
    public function addRows(array $data) {
        /** @var ThinkModel $this */
        $res = $this->strict(false)->limit(100)->insertAll($data);
        return $res;
    }

    /**
     * Notes: 更新数据，返回影响的记录数
     * Author: Uncle-L
     * Date: 2020/7/20
     * Time: 16:37
     * @param array $data
     * @param array $where
     * @return int|string
     */
    public function change(array $data, array $where = []) {
        /** @var ThinkModel $this */
        $query = $this->newQuery();
        $query = $this->buildWhere($query, $where);
        $res = $query->strict(false)->update($data);
        return $res;
    }

    /**
     * 根据主键/第一个key批量修改当前数据表数据
     * 示例：
     * 单主键
     * $res = $model->batchChange([
     *  ['xx_id'=>'xxxx','name'=>'小王'],
     *  ['xx_id'=>'xxxxxx','name'=>'小李','avatar'=>'http://xxx...']
     * ],'xx_id');
     * 联合主键
     * $res = $model->batchChange([
     *  ['xx_id'=>'xxxx','xxx_id'=>'xxxx','name'=>'小王'],
     *  ['xx_id'=>'xxxxxx','xxx_id'=>'xxxxxx','name'=>'小李','avatar'=>'http://xxx...']
     * ],['xx_id','xxx_id']);
     * @param array $datas
     * @param string|array|null $uniqueKeys 唯一键（也有可能为联合主键），不指定时默认为模型配置的pk。不包含全部指定主键的数据不会被修改
     * @param int $batchSize
     */
    public function batchChange(array $datas, $uniqueKeys = null, int $batchSize = 500) {
        /** @var ThinkModel $this */
        if (!$datas) return;
        $table = $this->getTable();
        $tableFields = $this->db()->getConnection()->getTableFields($table);
        $uniqueKeys = $uniqueKeys ?: $this->getPk();
        if (!is_array($uniqueKeys)) $uniqueKeys = [$uniqueKeys];
        // 获取要修改的所有字段
        $columns = [];
        foreach ($datas as $data) {
            $columns = array_merge($columns, array_keys($data));
        }
        $columns = array_unique($columns);
        if (!array_in($uniqueKeys, $columns)) return;
        foreach ($uniqueKeys as $uniqueKey) {
            unset($columns[array_search($uniqueKey, $columns)]);
        }
        if (!$columns || !($columns = array_intersect($columns, $tableFields))) return;
        Db::transaction(function () use ($datas, $uniqueKeys, $batchSize, $table, $columns) {
            foreach (array_chunk($datas, $batchSize) as $chunkDatas) {
                $bindings = [];
                $setSqls = [];
                foreach ($columns as $column) {
                    $setSql = "";
                    foreach ($chunkDatas as $data) {
                        $dataColumns = array_keys($data);
                        if (!array_key_exists($column,$data) || !array_in($uniqueKeys, $dataColumns)) continue;
                        $setSql .= "WHEN ";
                        $i = 0;
                        foreach ($uniqueKeys as $uniqueKey) {
                            $setSql .= ($i != 0 ? " AND " : "") . "`{$uniqueKey}` = ?";
                            $bindings[] = $data[$uniqueKey];
                            $i++;
                        }
                        $setSql .= " THEN ? ";
                        $bindings[] = $data[$column];
                    }
                    if (!$setSql) continue;
                    $setSql = "`{$column}` = CASE {$setSql} ELSE `{$column}` END ";
                    $setSqls[] = $setSql;
                }
                if (!$setSqls) continue;
                $whereSql = "";
                $i = 0;
                foreach ($uniqueKeys as $uniqueKey) {
                    $uniqueKeyVals = array_column($chunkDatas, $uniqueKey);
                    $uniqueKeyVals = array_unique($uniqueKeyVals);
                    $uniqueKeyInSql = rtrim(str_repeat('?,', count($uniqueKeyVals)), ',');
                    $whereSql .= ($i != 0 ? " AND " : "") . "`{$uniqueKey}` IN({$uniqueKeyInSql})";
                    $bindings = array_merge($bindings, $uniqueKeyVals);
                    $i++;
                }
                $sql = "UPDATE {$table} SET " . implode(',', $setSqls) . " WHERE {$whereSql}";
                Db::execute($sql, $bindings);
            }
        });
    }

    /**
     * Notes: 字段自增，返回影响数据的条数
     * Author: Uncle-L
     * Date: 2020/7/20
     * Time: 16:38
     * @param string $field
     * @param array $where
     * @param int $step
     * @return int|true
     */
    public function fieldInc(string $field, array $where = [], int $step = 1) {
        /** @var ThinkModel $this */
        $query = $this->newQuery();
        $query = $this->buildWhere($query, $where);
        $res = $query->inc($field, $step)->update();
        return $res;
    }

    /**
     * Notes: 字段自减，返回影响数据的条数
     * Author: Uncle-L
     * Date: 2020/7/20
     * Time: 16:38
     * @param string $field
     * @param array $where
     * @param int $step
     * @return int|true
     */
    public function fieldDec(string $field, array $where = [], int $step = 1) {
        /** @var ThinkModel $this */
        $query = $this->newQuery();
        $query = $this->buildWhere($query, $where);
        $res = $query->dec($field, $step)->update();
        return $res;
    }

    /**
     * Notes: 物理删除
     * Author: Uncle-L
     * Date: 2021/11/30
     * Time: 18:29
     * @param array $where
     * @return bool
     */
    public function del(array $where) {
        /** @var ThinkModel $this */
        $query = $this->newQuery();
        $query = $this->buildWhere($query, $where);
        $res = $query->delete();
        return $res;
    }

}